<?php

namespace Town\View;

/**
 * 解析器
 */
class TbParser
{
    // 模板根空间
    private $template_root_dir = null;

    // 模板名称
    private $template_name     = null;

    // 模板扩展名
    public  $template_ext      = 'html';

    // 转义字符
    private $escape_char = [
        '"' => '#town_quot_r1827382_numpure',
        "'" => '#town_apos_r1827382_numpure',
    ];

    // 模板内容
    private $content = null;

    // 文档结构
    private $struct  = null;
    

    public function __construct($conf)
    {
        if (!array_key_exists('template_root_dir', $conf)) 
            die('模板文件根目录不能为空');
        $this->template_root_dir= $conf['template_root_dir'];
        
        if (!array_key_exists('template_name', $conf)) 
            die('模板文件名称不能为空');
        $this->template_name = $conf['template_name'];

        $this->template_ext  = $conf['template_ext'] ?? 'html';

        // 初始化结构值
        $this->struct = [
            'div'   => [],
            'space' => [],
        ];
    }

    public function setName($name)
    {
        $this->template_name = $name;
    }


    /**
     * 处理...
     * @return [type] [description]
     */
    public function exec()
    {
        $this->content = $this->preproccess($this->template_name);

        // @inherit
        $this->inherit();

        // @include
        $this->include();

        // @space
        $this->space();

        // @args
        $this->base_args();

        // @foreach 和 @if 的层级关系
        $subject = $this->match_tags($this->content);

        // @反转义
        $subject = $this->escape($subject, false);
        $this->content = $subject;
        return $subject;
    }


    /**
     * 预处理
     * @return [type] [description]
     */
    private function preproccess($name)
    {
        $path = rtrim($this->template_root_dir, '/');
        $path = "{$path}/{$name}.{$this->template_ext}";
        if (!is_readable($path)) 
            throw new \Exception("模板文件不能读取:{$path}", 1);

        $content = file_get_contents($path);
        return $this->escape($content, true);
    }


    /**
     * 继承,单继承
     * @return [type] [description]
     */
    private function inherit()
    {
        $base = $this->content;
        $patt = '/@inherit\((.*?)\)/is';
        $number  = preg_match($patt, $base, $matches);
        if ($number > 0) {
            $path    = str_replace('.', '/', $matches[1]);
            $name    = $this->directory_path($path);
            $inherit = $this->preproccess($path).PHP_EOL;

            // 提出 base 有效的东西 即div块中的东西
            $div    = '/@div\(.*?\).*?@enddiv/is';
            $number = preg_match_all($div, $base, $matches);
            if ($number) {
                foreach ($matches[0] as $div_item) {
                    $inherit .= $div_item.PHP_EOL;
                }
            }
            $base = $inherit;
        }

        $this->content = $base;
    }


    /**
     * 包含文件
     * @return [type] [description]
     */
    private function include()
    {
        $subject = $this->content;

        // 全部的可遍历的模板
        $include = '/@include\((.*?)\)/is';

        // 处理 include
        $number = preg_match_all($include, $subject, $matches);
        if ($number) {
            foreach ($matches[1] as $key => $path) {
                $origin  = $matches[0][$key];
                $path    = $this->directory_path($path);
                $content = $this->preproccess($path);
                $subject = str_replace($origin, $content, $subject);
            }
            $this->content = $subject;
            $this->include();
        }

        return false;
    }


    /**
     * space 块替换,假设全部引入
     * @return [type]        [description]
     */
    private function space()
    {
        $subject = $this->content;

        // 1. 处理div
        $div    = '/@div\((.*?)\)(.*?)@enddiv/is';
        $number = preg_match_all($div, $subject, $matches);
        if ($number) {
            foreach ($matches[1] as $key => $name) {
                $this->struct['div'][$name] = $matches[2][$key];
                $subject = str_replace($matches[0][$key], '', $subject);
            }
        }

        // 2. 处理space
        $space  = '/@space\((.*?)\)/is';
        $number = preg_match_all($space, $subject, $matches);
        if ($number) {
            foreach ($matches[1] as $key => $item) {
                if (array_key_exists($item, $this->struct['div'])) {
                    $text    = $this->struct['div'][$item];
                    $subject = str_replace($matches[0][$key], $text, $subject);
                    unset($this->struct['div'][$item]);
                } else {
                    // 没有直接替换为空对象
                    $this->struct['space'][$item] = $item;
                    $subject = str_replace($matches[0][$key], '', $subject);
                }
            }
            $this->content = $subject;
        }

        return false;
    }


    /**
     * 参数处理
     * @return [type] [description]
     */
    private function base_args()
    {
        $subject = $this->content;

        $store = [];
        $chunk = function($patterns) use (&$subject, &$store) {
            $patterns = is_array($patterns) ? $patterns : [$patterns];

            foreach ($patterns as $pattern) {

                $number = preg_match_all($pattern, $subject, $matches);
                if (!$number)
                    continue;

                foreach ($matches[0] as $content) {
                    $uniqid  = uniqid ('chunk_');
                    $subject = str_replace($content, $uniqid, $subject);
                    $store[$uniqid] = $content;
                }
            }
        };

        // 获取loop和condition的数据块
        $patterns = [
            '/@foreach\s*\(.*?\).*?@endforeach/isx',
            '/@if\(.*?\).*?@fi/isx'
        ];
        $chunk($patterns);

        // 处理独立变量
        $pattern = '/{\$(.*?)}/is';
        $number = preg_match_all($pattern, $subject, $matches);
        if ($number) {
            foreach ($matches[1] as $key => $name) {
                $replace = "<?php echo \$this->args('{$name}'); ?>";
                $subject = str_replace($matches[0][$key], $replace, $subject);
            }
        }

        // 回填数据块
        foreach ($store as $key => $chunk) {
            $subject = str_replace($key, $chunk, $subject);
        }

        $this->content = $subject;
    }
    /**
     * 多标签层次匹配
     * @param  string $text  文本
     * @param  array  $tags  标签
     * @param  array  $level 当前层数
     * @return array
     */
    public function match_tags($text = '', $scope = [])
    {
        // 查找第一个 tags,和tag第一个结束福,然后回滚
        $map  = ['@foreach' => '@endforeach', '@if' => '@fi'];
        $tags = $this->tags($text, $map);
        if (is_bool($tags)) {
            return $text;
        }

        // 处理头部
        $obj = $this;
        $topIf = function($subject, $scope) use ($obj){
            $pattern = '/@if\(.*?\)/is';
            $number1 = preg_match($pattern, $subject, $matches);
            if ($number1 < 1)
                return $subject;
            $content = trim($matches[0], '@');
            $subject = str_replace($matches[0], "<?php {$content} { ?>", $subject);
            $args = $obj->get_var($content);
            $args = is_array($args) ? $args : [$args];
            foreach ($args as $k => $v) {
                if (!$obj->in_scope($v, $scope)) {
                    $replace = "\$this->args('{$v}')";
                    $subject = str_replace($v, $replace, $subject);
                }
            }

            // TODO::处理产生新变量的问题

            return [$subject, $scope];
        };
        $topEF = function($subject, $scope) use ($obj){

            $pattern = '/@ef\s*(\(.*?\))/is';
            $number1 = preg_match($pattern, $subject, $matches);
            if ($number1 < 1)
                return $subject;

            $content = trim($matches[0], 'ef@');
            $subject = str_replace($matches[0], "<?php }elseif{$content} { ?>", $subject);
            $args = $obj->get_var($content);
            $args = is_array($args) ? $args : [$args];
            foreach ($args as $k => $v) {
                if (!$obj->in_scope($v, $scope)) {
                    $replace = "\$this->args('{$v}')";
                    $subject = str_replace($v, $replace, $subject);
                }
            }

            // TODO::处理产生新变量的问题

            return [$subject, $scope];
        };

        $topLoop = function($subject, $scope) use ($obj){
            $pattern = '/@foreach \s* \((.*?) \s+ as \s+ (.*?) (=>.*?)?\)/isx';
            $number1 = preg_match($pattern, $subject, $matches);
            if ($number1) {
                $for     = trim($matches[0]);
                $replace = "<?php ".substr($for, 1)." { ?>";
                $subject = str_replace($for, $replace, $subject);

                $list    = trim($matches[1]);
                $replace = "\$this->args('".ltrim($list, '$')."') ";
                $subject = str_replace($matches[1], $replace, $subject);

                $args[] = $list;
                $args[] = trim($matches[2]);
                if (count($matches) == 4) {
                    $args[] = trim(strstr($matches[3], '$'));
                }

                // TODO:需要处理list
            }
            return [$subject, $args];
        };

        list($state, $tag, $idx) = $tags;


        // 处理参数
        switch ($state) {
            case 'start': {
                switch ($tag) {
                    case '@if':
                        $pos = strpos($text, PHP_EOL, $idx);
                        list($top, $args) = $topIf(substr($text, 0, $pos), $scope);
                        // array_push($scope, ...$args);
                        $mid = $this->match_tags(substr($text, $pos), $scope);
                        $text= $top.$mid;
                        break;

                    case '@foreach':
                        $pos = strpos($text, PHP_EOL, $idx);
                        list($top, $args) = $topLoop(substr($text, 0, $pos), $scope);
                        array_push($scope, ...$args);
                        $mid = $this->match_tags(substr($text, $pos), $scope);
                        $text= $top.$mid;
                        break;

                    default:
                        die('match_tags error:'.$tag);
                }
            }
                break;

            case 'end':{
                switch ($tag) {
                    case '@fi':
                        $text = substr_replace($text, '<?php } ?>', $idx, 3);
                        break;

                    case '@endforeach':
                        $text = substr_replace($text, '<?php } ?>', $idx, 11);
                        break;
                    
                    default:
                        die('match_tags error:'.$tag);
                }
            }
                break;
            case 'mid':
                switch ($tag) {
                    case '@ef':
                        $pos = strpos($text, PHP_EOL, $idx);
                        list($top, $args) = $topEF(substr($text, 0, $pos), $scope);
                        $mid = $this->match_tags(substr($text, $pos), $scope);
                        $text= $top.$mid;
                        break;
                    case '@el':
                        $text = substr_replace($text, '<?php }else{ ?>', $idx, 3);
                        break;
                    case '@empty':
                        # code...
                        break;
                    default:
                        die('match_tags error:'.$tag);
                        break;
                }
                break;

            default:
                die('match_tags error:'.$tag);
        }
        if (count($scope)) {
            $text = $this->args($text, $scope);
        }

        // 处理平行参数
        while ($tags = $this->tags($text, $map)) {
            $text = $this->match_tags($text);
        }

        return $text;

    }

    public function args($subject, $scope)
    {
        // 处理独立变量
        $pattern = '/{(\$.*?)}/is';
        $number = preg_match_all($pattern, $subject, $matches);
        if ($number) {
            foreach ($matches[1] as $key => $name) {
                $val = $matches[0][$key];
                if ($this->in_scope($name, $scope)) {
                    $replace = "<?php echo ({$name}); ?>";
                    $subject = str_replace($val, $replace, $subject);
                } else {
                    $name  = ltrim($name, '$');
                    $replace = "<?php echo \$this->args('{$name}'); ?>";
                    $subject = str_replace($val, $replace, $subject);
                }
            }
        }
        return $subject;
    }


    /**
     * 是否在该函数中
     * @param  mixid  $arg  参数
     * @param  array  $scope 范围数组
     * @return [type]       [description]
     */
    private function in_scope($arg, $scope = [])
    {
        if (empty($scope) || empty($arg))
            return false;

        $val = $this->get_var($arg);
        return in_array($val, $scope);
    }


    /**
     * 从字符串中提取像变量名的字符串
     * @param  string $arg [description]
     * @return [type]      [description]
     */
    private function get_var($str = '', $pos = true)
    {
        // 是否是 \w
        $isAZ_ = function($char) {
            $ascii = ord($char);
            return ($ascii > 64 && $ascii < 91)
                || ($ascii > 96 && $ascii < 123)
                || ($ascii == 95);
        };

        $val = '';
        $end = true;
        $start = false;
        $args= [];
        $len = strlen($str);
        for ($i = 0; $i < $len; $i++) {
            $ch = $str[$i];

            if ($isAZ_($ch) && $start) {
                $val .= $ch;
                continue;
            }

            if ($start && !$isAZ_($ch)) {
                $args[] = $val;
                $val    = '';
                $end    = true;
                $start  = false;
                continue;
            }

            if ($end
                && ($i<1 || !$isAZ_($str[$i-1]))
                && ($ch=='$')) {
                $start = true;
                $val   = $ch;
                continue;
            }
        }
        if ($start) {
            $args[] = $val;
        }

       return (count($args) == 1) ? $args[0] : $args;
    }


    /**
     * 匹配到start_tag或则匹配到end_tag
     * 
     * @param  string $str  字符串
     * @param  array  $tags 标记
     * @return [type]       [description]
     */
    public function tags($str, $tags = [])
    {
        $mid = ['@el', '@ef', '@empty'];
        foreach ($mid as &$val) {
            $val = [$val, strlen($val)];
        }
        $mlen= count($mid);

        $s_tags  = array();
        $e_tags  = array();
        foreach ($tags as $start => $end) {
            $s_tags[] = [$start, strlen($start)];
            $e_tags[] = [$end, strlen($end)];
        }

        $slength = strlen($str);
        $tcount  = count($tags);
        for ($i=0; $i < $slength; $i++) { 
            $idx = $i;

            // start_tag case: @foreach
            for ($j=0; $j < $tcount; $j++) { 

                $i = $idx;
                $k = 0;
                $c = '';
                list($tag, $tlength) = $s_tags[$j];
                while ($i < $slength
                    && $k < $tlength
                    && $str[$i] == $tag[$k]) {
                    $c .= $str[$i];
                    $k++;
                    $i++;
                }
                if ($tag == $c) {
                    return ['start', $c, $idx];
                }
            }

            // end_tag case: @endforeach
            for ($j=0; $j < $tcount; $j++) { 

                $i = $idx;
                $k = 0;
                $c = '';
                list($tag, $tlength) = $e_tags[$j];
                while ($i < $slength
                    && $k < $tlength
                    && $str[$i] == $tag[$k]) {
                    $c .= $str[$i];
                    $k++;
                    $i++;
                }
                if ($tag == $c) {
                    return ['end', $c, $idx];
                }
            }

            for ($j=0; $j < $mlen; $j++) { 
                $i = $idx;
                $k = 0;
                $c = '';
                list($tag, $tlength) = $mid[$j];
                while ($i < $slength
                    && $k < $tlength
                    && $str[$i] == $tag[$k]) {
                    $c .= $str[$i];
                    $k++;
                    $i++;
                }
                if ($tag == $c) {
                    return ['mid', $c, $idx];
                }
            }
        }
        return false;
    }


    /**
     * 转义
     * " ---> #town_quot_r1827382;
     * ' ---> #town_apos_r1827382;
     * @param  string  $text 要转义的字符
     * @param  boolean $done 转义&反转义
     * @return string
     */
    private function escape($text, $done = true)
    {
        $origin = array_keys($this->escape_char);
        $escape = array_values($this->escape_char);
        if ($done)
            $text = str_replace($origin, $escape, $text);
        else
            $text = str_replace($escape, $origin, $text);

        return $text;
    }


    /**
     * 处理路径
     * @param  [type] $path [description]
     * @return [type]       [description]
     */
    private function directory_path($path)
    {
        $ds   = DIRECTORY_SEPARATOR;
        $path = str_replace('.', $ds, rtrim($path, $ds));
        return $path;
    }
}